home *** CD-ROM | disk | FTP | other *** search
/ Network Supervisor's Toolkit / Network Supervisor's Toolkit.iso / tools / nwtp06 / nwacct.pas < prev    next >
Pascal/Delphi Source File  |  1996-07-10  |  20KB  |  561 lines

  1. {$X+,B-,V-,S-} {essential compiler directives}
  2.  
  3. Unit nwAcct;
  4.  
  5. { nwAcct unit as of 950301 / NwTP 0.6 API. (c) 1993,1995, R.Spronk }
  6.  
  7. INTERFACE
  8.  
  9. Uses nwIntr,nwMisc,nwBindry,nwConn;
  10.  
  11. { Primary functions:                  Interrupt: Comments:
  12.  
  13. * GetAccountStatus                    (F217/96)  (1)
  14. * SubmitAccountCharge                 (F217/97)  (2)(3)
  15. * SubmitAccountHold                   (F217/98)  (2)
  16. * SubmitAccountNote                   (F217/99)  (2)
  17.  
  18.   Secondary functions:
  19.  
  20. * AccountingInstalled    (4)
  21. * SetAccountStatus       (5)
  22. * AddAccountingServer    (5)
  23. * DeleteAccountingServer (5)
  24. * DeleteAccountHolds     (2)
  25.  
  26.   Notes: (1) To be called by:
  27.              -accounting servers;
  28.              -supervisor equivalent users;
  29.              -objects querying their own account status.
  30.          (2) To be called by accounting servers only.
  31.          (3) Can be imitated by supervisor-equivalent users by
  32.              calling GetAccountStatus and SetAccountStatus. Atomicity
  33.              of such a bindery transaction can not be guaranteed.
  34.          (4) Can be called by all logged on users.
  35.          (5) Supervisor equivalent users only.
  36.  
  37. }
  38.  
  39. Var result:word;
  40.  
  41. { Type definitions based on NET$ACCT.FMT by Wolfgang Schreiber   }
  42. { See Acct.pas in the XACCT archive for an example of their use. }
  43.  
  44. CONST { Accounting file record types }
  45.       RT_SUBMIT_CHARGE=1;
  46.       RT_ACCOUNT_NOTE =2;
  47.  
  48.       { comment types within accounting file }
  49.  
  50.       CT_CONN_CHARGE    = 1;
  51.       CT_STORAGE_CHARGE = 2;
  52.       CT_LOGIN_NOTE     = 3;
  53.       CT_LOGOUT_NOTE    = 4;
  54.       CT_INTRUDER_NOTE  = 5;
  55.       CT_TIMEMOD_NOTE   = 6;
  56.       CT_BOOT_NOTE      = 8;
  57.       CT_DOWN_NOTE      = 9;
  58.       CT_COMMENT        = 99;
  59.  
  60. Type TAccDateTime6 = Array [1..6] of Byte; { date and time stamp of entry YMDHMS}
  61.  
  62. Type TComment = RECORD   { interprete comments according to CmtType }
  63.      CASE Integer of
  64.      CT_CONN_CHARGE    : (ConnectTime  : LongInt;
  65.                           RequestCount : LongInt;
  66.                           BytesRead    : Array[1..6] of BYTE;  {hi-lo}
  67.                           BytesWritten : Array[1..6] of BYTE); {hi-lo}
  68.      CT_STORAGE_CHARGE : (BlocksOwned  : LongInt;
  69.                           HalfHours    : LongInt);
  70.      CT_LOGIN_NOTE,
  71.      CT_LOGOUT_NOTE,
  72.      CT_INTRUDER_NOTE  : (Net :TnetworkAddress;
  73.                           Node:TnodeAddress);
  74.      CT_TIMEMOD_NOTE   : (ServerTime   : TAccDateTime6);
  75.      CT_BOOT_NOTE,
  76.      CT_DOWN_NOTE      : ();{ NO comment fields }
  77.      CT_COMMENT        : (Comment      : String)
  78.      END;
  79.  
  80. {  Use either the Type SubmitCharge or SubmitNote to interprete
  81.    an entry - decide on typecasting with the aid of the RecType field. }
  82.  
  83. Type TChargeRecord = RECORD
  84.        Length        : Word;
  85.        ServerObjId   : LongInt;    {hi-lo}
  86.        TimeStamp     : TAccDateTime6;
  87.        RecType       : BYTE;       {Record type Note/Charge}
  88.        ccode         : BYTE;       {completion code}
  89.        ServiceType   : WORD;       {hi-lo}
  90.        ClientObjID   : LongInt;    {hi-lo}
  91.        Charge        : LongInt;    {hi-lo}
  92.        CommentType   : WORD;       {hi-lo}
  93.        Comment       : Tcomment;   {Variable length field}
  94.        END;
  95.  
  96. Type TNoteRecord = RECORD
  97.        Length       : Word;
  98.        ServerObjId  : LongInt;     {hi-lo}
  99.        TimeStamp    : TAccDateTime6;
  100.        RecType      : BYTE;
  101.        ccode        : BYTE;
  102.        ServiceType  : WORD;        {hi-lo}
  103.        ClientObjID  : LongInt;     {hi-lo}
  104.        CommentType  : WORD;        {hi-lo}
  105.        Comment      : TComment;
  106.        END;
  107.  
  108.  
  109. {F217/96 [2.15c+]}
  110. Function GetAccountStatus(objName:string; objType:word;
  111.                           Var balance,limit,holds:LongInt):boolean;
  112.  
  113. {F217/97 [2.15c+]}
  114. Function SubmitAccountCharge(objName:string; objType:word;
  115.                              charge,cancelHoldAmount:Longint;
  116.                              serviceType, commentType:word; comment:string):boolean;
  117.  
  118. {F217/98 [2.15c+]}
  119. Function SubmitAccountHold(objName:string; objType:word;
  120.                            reserveAmount:Longint         ):boolean;
  121.  
  122. {F217/99 [2.15c+]}
  123. Function SubmitAccountNote(objName:string; objType:word;
  124.                            serviceType,commentType:word; comment:string):boolean;
  125.  
  126. {--------Secondary Functions-----------------------------------------------}
  127.  
  128. Function AccountingInstalled:boolean;
  129.  
  130. Function SetAccountStatus(objName:string; objType:word; balance,limit:LongInt):boolean;
  131. { need to be supervisor equivalent to use this call }
  132.  
  133. Function AddAccountingServer(objName:string;objType:word):boolean;
  134. { need to be supervisor equivalent to use this call }
  135.  
  136. Function DeleteAccountingServer(objName:string;objType:word):boolean;
  137. { need to be supervisor equivalent to use this call }
  138.  
  139. Function DeleteAccountHolds(objName:string; objType:word):boolean;
  140. { delete all holds the caller (an accounting server) has on the
  141.   object with name objName of type objType. }
  142.  
  143. Type Tcharge=record
  144.              DaysOfCharge:Byte; { bit 0=sunday,.. bit 6=saturday }
  145.              TimeOfCharge:Byte; { 0:00=0 ..23:30 =47, half-hour
  146.                                   during which the specified 'new' rate takes effect. }
  147.              ChargeRateMultiplier,
  148.              ChargeRateDivisor:Word;
  149.              end;
  150.      TchargeRec=record
  151.                 NextChargeTime:Longint; { minutes since 1-1-1985 }
  152.                 charges:array[1..20] of Tcharge;
  153.                 end;
  154.  
  155.  
  156. Type TchargeTableEntry=array[0..47] of Real;
  157. Var ChargeTable:Array [0..6] of TchargeTableEntry;
  158.  
  159. IMPLEMENTATION {===========================================================}
  160.  
  161. Procedure GetBindryAccountStatus(objName:string; objType:word;
  162.                                 Var balance,limit,holds:LongInt);
  163. { called by GetAccountStatus when the calling object isn't an
  164.   accounting server. The F217/96 fails, but a bindery read will
  165.   work for supervisor-equivalent users. }
  166. Var accPropVal:Tproperty;
  167.     accVal: record
  168.             _balance:LongInt; {hi-lo}
  169.             _limit:LongInt;   {hi-lo}
  170.             _Reserved:array[1..120] of byte; { NW internal info }
  171.             end ABSOLUTE accPropVal;
  172.     holdPropVal:Tproperty;
  173.     holdVal: array[1..16]
  174.               of record
  175.                  AccountServerID:Longint; {hi-lo}
  176.                  HoldAmount     :LongInt; {hi-lo}
  177.                  end ABSOLUTE holdPropVal;
  178.     moreSegments:boolean;
  179.     t,propFlags:byte;
  180. begin
  181. IF ReadPropertyValue(objName,objType,'ACCOUNT_BALANCE',1,
  182.                     accPropVal,moreSegments,propFlags)
  183.   then begin
  184.        balance:=Lswap(accVal._balance);
  185.        limit:=Lswap(accVal._limit);
  186.        IF ReadPropertyValue(objName,objType,'ACCOUNT_HOLDS',1,
  187.                             holdPropVal,moreSegments,propFlags)
  188.         then begin { holds exist. }
  189.              holds:=0;
  190.              for t:=1 to 16
  191.               do if holdVal[t].AccountServerID<>0
  192.                  then holds:=holds+Lswap(holdVal[t].HoldAmount);
  193.              end;
  194.        if nwBindry.result=$FB
  195.          then begin
  196.               result:=0;
  197.               holds:=0;
  198.               end
  199.          else result:=nwBindry.result;
  200.        end
  201.   else if nwBindry.result=$FB { no such property }
  202.         then result:=$C1
  203.         else if nwBindry.result=$F1 { invalid bindery security }
  204.              then result:=$C0
  205.              else result:=nwBindry.result;
  206. { resultcodes: 00 success; C0 No Account Privileges; C1 No Account Balance;
  207.   96 Server Out Of memory; FC No Such Object; FE Server Bindery Locked;
  208.   FF Bindery Failure}
  209. end;
  210.  
  211.  
  212. {F217/96 [2.15c+]}
  213. Function GetAccountStatus(objName:string; objType:word;
  214.                           Var balance,limit,holds:LongInt):boolean;
  215. { equivalent to reading the ACCOUNT_BALANCE and ACCOUNT_HOLDS properties
  216.   of the object. The properties may not exist. }
  217. { This function will be successful if:
  218.      a) the caller is an accounting server on the current fileserver
  219.   OR b) the caller is supervisor-equivalent
  220.   OR c) the caller is querying his own account status }
  221. Type Treq=record
  222.           len:word;
  223.           subF:byte;
  224.           _objType:word; {hi-lo}
  225.           _objName:string[48];
  226.           end;
  227.      Trep=record
  228.           _balance: LongInt; {hi-lo}
  229.           _limit  : Longint; {hi-lo}
  230.           reserved: array [1..120] of byte;
  231.           _holds  : array [1..16]
  232.                      of record
  233.                         serverObjId:LongInt; {hi-lo}
  234.                         HoldAmount :LongInt  {hi-lo}
  235.                         end;
  236.           end;
  237.      TPreq=^Treq;
  238.      TPrep=^Trep;
  239. Var t:byte;
  240. begin
  241. With TPreq(GlobalReqBuf)^
  242.  do begin
  243.     len:=sizeOf(Treq)-2;
  244.     subf:=$96;
  245.     _objType:=swap(objType); { force hi-lo}
  246.     PstrCopy(_objName,objName,48); UpString(_objName);
  247.     end;
  248. F2SystemCall($17,sizeOf(Treq),sizeOf(Trep),result);
  249. With TPrep(GlobalReplyBuf)^
  250.  do begin
  251.     balance:=Lswap(_balance); { force lo-hi again }
  252.     limit:=Lswap(_limit); { force lo-hi again }
  253.     holds:=0;
  254.     for t:=1 to 16
  255.      do if _holds[t].serverObjId<>0
  256.       then holds:=holds+Lswap(_holds[t].holdAmount); { force lo-hi again }
  257.     end;
  258. IF result=$C0 { no account privileges }
  259.  then GetBindryAccountStatus(objName,objType,balance,limit,holds);
  260.       { try to read status not as an accounting server, but as a supervisor }
  261. GetAccountStatus:=(result=0);
  262. { resultcodes: 00 success; C0 No Account Privileges; C1 No Account Balance }
  263. end;
  264.  
  265.  
  266. {F217/97 [2.15c+]}
  267. Function SubmitAccountCharge(objName:string; objType:word;
  268.                              charge,cancelHoldAmount:Longint;
  269.                              serviceType, commentType:word; comment:string):boolean;
  270. { -The cancelHold amount should be exactly the same as the amount that
  271.    was put on huld with the SubmitAccountHold call. If no
  272.    SubmitAccountHold call was made, the cancelHoldAmount should be set to zero.
  273.   -'negative charges' are allowed. They will increase the balance of
  274.    the object objName of objType.
  275.   -Use the objectType of caller for the serviceType parameter.
  276.    (audit log purposes)
  277.   -Set commentType to 0 and comment to '' if you aren't interested in the
  278.    audit log.
  279.   -To be called by accounting servers only.
  280.   -Can be imitated by supervisor-equivalent users by
  281.    calling GetAccountStatus and SetAccountStatus. Atomicity
  282.    of such a bindery transcation can not be guaranteed.
  283.  
  284.    }
  285. Type Treq=record
  286.           len :word;
  287.           subf:byte;
  288.           _serviceType:word;    {hi-lo}
  289.           _charge     :Longint; {hi-lo}
  290.           _cancelHold :Longint; {hi-lo}
  291.           _objType    :word;    {hi-lo}
  292.           _commentType:word;    {hi-lo}
  293.           _objNameAndComment:Array[1..305] of char;
  294.           end;
  295.     TPreq=^Treq;
  296. Var p:byte;
  297. begin
  298. With TPreq(GlobalReqBuf)^
  299.  do begin
  300.     subf:=$97;
  301.     _serviceType:= swap(serviceType);      {force hi-lo}
  302.     _charge     :=Lswap(charge);           {force hi-lo}
  303.     _cancelHold :=Lswap(cancelHoldAmount); {force hi-lo}
  304.     _objType    := swap(objType);          {force hi-lo}
  305.     _commentType:= swap(commentType);      {force hi-lo}
  306.     p:=ord(objName[0]);if p>48 then p:=48;
  307.     UpString(objName);
  308.     Move(objname[0],_objNameandComment[1],p+1);
  309.     Move(comment[0],_objNameandComment[p+2],ord(comment[0])+1);
  310.     len:=15+p+1+ord(comment[0])+1;
  311.     F2SystemCall($17,len+2,0,result);
  312.     end;
  313. SubmitAccountCharge:=(result=$00);
  314. { resultcodes: 00 successful; C0 No Account Privileges;
  315.                C1 No Account Balance; C2 Credit Limit Exceeded. }
  316. end;
  317.  
  318.  
  319. {F217/98 [2.15c+]}
  320. Function SubmitAccountHold(objName:string; objType:word;
  321.                            reserveAmount:Longint         ):boolean;
  322. { To be called by accounting servers only. }
  323. Type Treq=record
  324.           len :word;
  325.           subf:byte;
  326.           _reserveAmount:Longint; {hi-lo}
  327.           _objType:word; {hi-lo}
  328.           _objName:string[48];
  329.           end;
  330.      TPreq=^Treq;
  331. Var p:byte;
  332. begin
  333. With TPreq(GlobalReqBuf)^
  334.  do begin
  335.     subf:=$98;
  336.     _reserveAmount:=Lswap(ReserveAmount); { force hi-lo}
  337.     _objType:=swap(objType); { force hi-lo }
  338.     p:=ord(objName[0]); if p>48 then p:=48;
  339.     _objName:=objname;UpString(_objName);_objName[0]:=chr(p);
  340.     len:=7+p+1;
  341.     F2SystemCall($17,len+2,0,result);
  342.     end;
  343. SubmitAccountHold:=(result=$00);
  344. { resultcodes: 00 successful; C0 No Account Privileges;
  345.                C1 No Account Balance; C2 Credit Limit Exceeded.
  346.                C3 Account Too Many Holds }
  347. end;
  348.  
  349. {F217/99 [2.15c+]}
  350. Function SubmitAccountNote(objName:string; objType:word;
  351.                            serviceType,commentType:word; comment:string):boolean;
  352. { To be called by accounting servers only.}
  353. Type Treq=record
  354.          len:word;
  355.          subf:byte;
  356.          _serviceType:word; {hi-lo}
  357.          _objType:word; {hi-lo}
  358.          _commentType:word; {hi-lo}
  359.          _objNameAndComment:array[1..305] of char;
  360.          end;
  361.      TPreq=^Treq;
  362. Var p:byte;
  363. begin
  364. with TPreq(GlobalReqBuf)^
  365.  do begin
  366.     subf:=$99;
  367.     _serviceType:= swap(serviceType);      {force hi-lo}
  368.     _objType    := swap(objType);          {force hi-lo}
  369.     _commentType:= swap(commentType);      {force hi-lo}
  370.     p:=ord(objName[0]);if p>48 then p:=48;
  371.     UpString(objName);
  372.     Move(objname[0],_objNameandComment[1],p+1);
  373.     Move(comment[0],_objNameandComment[p+2],ord(comment[0])+1);
  374.     len:=7+p+1+ord(comment[0])+1;
  375.     F2SystemCall($17,len+2,0,result);
  376.     end;
  377. SubmitAccountNote:=(result=0);
  378. {resultcodes: 00 Successful; C0 No Account Privileges }
  379. end;
  380.  
  381. {---------------- Secondary Functions--------------------------------------}
  382.  
  383.  
  384. Function AccountingInstalled:boolean;
  385. Var propVal:Tproperty;
  386.     connId:byte;
  387.     moreSegments:boolean;
  388.     propFlags:byte;
  389.     currServerName:string;
  390. begin
  391. IF NOT GetEffectiveConnectionID(ConnId)
  392.   then result:=nwConn.result
  393.   else if NOT GetFileServerName(ConnId,currServerName)
  394.         then result:=nwConn.result
  395.         else begin
  396.              ReadPropertyValue(currServerName,OT_FILE_SERVER,'ACCOUNT_SERVERS',1,
  397.                                propVal,moreSegments,propFlags);
  398.              result:=nwBindry.result;
  399.              end;
  400. AccountingInstalled:=(result=0);
  401. end;
  402.  
  403.  
  404. Function SetAccountStatus(objName:string; objType:word; balance,limit:LongInt):boolean;
  405. { will change the account status to reflect the given parameters.
  406.   any holds will not be changed.
  407.   You need to be supervisor-eq. to do this...}
  408. Var accPropVal:Tproperty;
  409.     accVal: record
  410.             _balance:LongInt; {hi-lo}
  411.             _limit:LongInt;   {hi-lo}
  412.             _Reserved:array[1..120] of byte; { NW internal info }
  413.             end ABSOLUTE accPropVal;
  414.     OldBalance,OldLimit,OldHolds:LongInt;
  415.     moreSegments:boolean;
  416.     propFlags:byte;
  417. begin
  418. IF ReadPropertyValue(objName,objType,'ACCOUNT_BALANCE',1,
  419.                     accPropVal,moreSegments,propFlags)
  420.   then begin
  421.        accVal._balance:=Lswap(balance); { force hi-lo}
  422.        accVal._limit:=Lswap(limit); { force hi-lo}
  423.        WritePropertyValue(objName,objType,'ACCOUNT_BALANCE',
  424.                           1,accPropVal,FALSE);
  425.        if (nwBindry.result=$F1) or (nwBindry.result=$F8)
  426.          then result:=$C0
  427.          else result:=nwBindry.result;
  428.        end
  429.   else if nwBindry.result=$FB { no such property }
  430.         then result:=$C1
  431.         else if nwBindry.result=$F1 { invalid bindery security }
  432.              then result:=$C0
  433.              else result:=nwBindry.result;
  434. SetAccountStatus:=(result=$00);
  435. { resultcodes: 00 success; C0 No Account Privileges; C1 No Account Balance;
  436.   96 Server Out Of memory; FC No Such Object; FE Server Bindery Locked;
  437.   FF Bindery Failure}
  438. end;
  439.  
  440.  
  441. Function AddAccountingServer(objName:string;objType:word):boolean;
  442. Var ConnId:byte;
  443.     currServerName:string;
  444. begin
  445. IF NOT GetEffectiveConnectionID(ConnId)
  446.    then result:=nwConn.result
  447.    else if NOT GetFileServerName(ConnId,currServerName)
  448.            then result:=nwConn.result
  449.            else begin
  450.                 AddBinderyObjectToSet(currServerName,OT_FILE_SERVER,'ACCOUNT_SERVERS',
  451.                                       objName,objType);
  452.                 result:=nwBindry.result;
  453.                 end;
  454. AddAccountingServer:=(result=0);
  455. end;
  456.  
  457. Function DeleteAccountingServer(objName:string;objType:word):boolean;
  458. Var ConnId:byte;
  459.     currServerName:string;
  460. begin
  461. IF NOT GetEffectiveConnectionID(ConnId)
  462.    then result:=nwConn.result
  463.    else if NOT GetFileServerName(ConnId,currServerName)
  464.            then result:=nwConn.result
  465.            else begin
  466.                 DeleteBinderyObjectFromSet(currServerName,OT_FILE_SERVER,'ACCOUNT_SERVERS',
  467.                                            objName,objType);
  468.                 result:=nwBindry.result;
  469.                 end;
  470. DeleteAccountingServer:=(result=0);
  471. end;
  472.  
  473. {F217/96 }
  474. Function DeleteAccountHolds(objName:string; objType:word):boolean;
  475. { delete all holds the caller (an accounting server) has on the
  476.   object with name objName of type objType. }
  477. Type Treq=record
  478.           len:word;
  479.           subF:byte;
  480.           _objType:word; {hi-lo}
  481.           _objName:string[48];
  482.           end;
  483.      Trep=record
  484.           _balance: LongInt; {hi-lo}
  485.           _limit  : Longint; {hi-lo}
  486.           reserved: array [1..120] of byte;
  487.           _holds  : array [1..16]
  488.                      of record
  489.                         serverObjId:LongInt; {hi-lo}
  490.                         HoldAmount :LongInt  {hi-lo}
  491.                         end;
  492.           end;
  493.      TPreq=^Treq;
  494.      TPrep=^Trep;
  495. Var t:byte;
  496.     holds:LongInt;
  497.     level:byte;
  498.     accServerId:LongInt;
  499.     accServerType:word;
  500.     accServerName:string;
  501. begin
  502. GetBinderyAccessLevel(Level,accServerID);
  503. GetBinderyObjectName(accServerID,accServerName,accServerType);
  504. With TPreq(GlobalReqBuf)^
  505.   do begin
  506.      len:=sizeOf(Treq)-2;
  507.      subf:=$96;
  508.      _objType:=swap(objType); { force hi-lo}
  509.      PstrCopy(_objName,objName,48); UpString(_objName);
  510.      end;
  511. F2SystemCall($17,sizeOf(Treq),sizeOf(Trep),result);
  512. if result=0
  513.  then With TPrep(GlobalReplyBuf)^
  514.       do begin
  515.          holds:=0;
  516.          for t:=1 to 16
  517.           do if accServerID=Lswap(_holds[t].serverObjId)
  518.            then holds:=holds+Lswap(_holds[t].holdAmount); { force lo-hi again }
  519.          if holds<>0
  520.           then SubmitAccountCharge(objName,objType,0,holds,
  521.                                    accServerType,0,'clearing holds');
  522.          end;
  523. DeleteAccountHolds:=(result=0);
  524. { resultcodes: 00 success; C0 No Account Privileges; C1 No Account Balance }
  525. end;
  526.  
  527.  
  528. Function GetConnectTimeCharge(Var currentCharge:Real;Var chargeRec:TchargeRec):boolean;
  529. Var propVal:Tproperty;
  530.     _chargeRec:TchargeRec             ABSOLUTE propVal;
  531.     _currcharge:record
  532.                 fill:LongInt;
  533.                 currMult,currDiv:word; {hi-lo}
  534.                 end                   ABSOLUTE propVal;
  535.     connId:byte;
  536.     moreSegments:boolean;
  537.     propFlags:byte;
  538.     currServerName:string;
  539. begin
  540. IF NOT GetEffectiveConnectionID(ConnId)
  541.    then result:=nwConn.result
  542.    else if NOT GetFileServerName(ConnId,currServerName)
  543.            then result:=nwConn.result
  544.            else if ReadPropertyValue(currServerName,OT_FILE_SERVER,
  545.                                      'CONNECT_TIME',1,
  546.                                      propVal,moreSegments,propFlags)
  547.                 then begin
  548.                      IF _currCharge.currDiv=0
  549.                       then currentCharge:=0
  550.                       else currentCharge:=Swap(_currCharge.currMult)/Swap(_currCharge.currDiv);
  551.                      move(propVal[9],propVal[5],124);
  552.                      chargeRec:=_chargeRec;
  553.                      result:=0;
  554.                      end
  555.                 else result:=nwBindry.result;
  556. GetConnectTimeCharge:=(result=0);
  557. end;
  558.  
  559.  
  560.  
  561. end.